package cn.jly.boot.mp.config;

import cn.jly.boot.mp.cron.QuartzSimpleSchedulerJob;
import cn.jly.boot.mp.cron.scheduler.CronSchedulerJob;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * Quartz 体系结构
 * 明白 Quartz 怎么用，首先要了解 Job（任务）、JobDetail（任务信息）、Trigger（触发器）和 Scheduler（调度器）这 4 个核心的概念。
 * <p>
 * （1）Job：是一个接口，只定义一个方法 execute（JobExecutionContext context），在实现接口的 execute 方法中编写所需要定时执行的 Job（任务），JobExecutionContext 类提供了调度应用的一些信息；Job 运行时的信息保存在 JobDataMap 实例中。
 * <p>
 * （2）JobDetail：Quartz 每次调度 Job 时，都重新创建一个 Job 实例，因此它不接受一个 Job 的实例，相反它接收一个 Job 实现类（JobDetail，描述 Job 的实现类及其他相关的静态信息，如 Job 名字、描述、关联监听器等信息），以便运行时通过 newInstance() 的反射机制实例化 Job。
 * <p>
 * （3）Trigger：是一个类，描述触发 Job 执行的时间触发规则，主要有 SimpleTrigger 和 CronTrigger 这两个子类。当且仅当需调度一次或者以固定时间间隔周期执行调度，SimpleTrigger 是最适合的选择；而 CronTrigger 则可以通过 Cron 表达式定义出各种复杂时间规则的调度方案：如工作日周一到周五的 15：00 ~ 16：00 执行调度等。
 * <p>
 * （4）Scheduler：调度器就相当于一个容器，装载着任务和触发器，该类是一个接口，代表一个 Quartz 的独立运行容器，Trigger 和 JobDetail 可以注册到 Scheduler 中，两者在 Scheduler 中拥有各自的组及名称，组及名称是 Scheduler 查找定位容器中某一对象的依据，Trigger 的组及名称必须唯一，JobDetail 的组和名称也必须唯一（但可以和 Trigger 的组和名称相同，因为它们是不同类型的）。Scheduler 定义了多个接口方法，允许外部通过组及名称访问和控制容器中 Trigger 和 JobDetail。
 * <p>
 * Job 为作业的接口，为任务调度的对象；JobDetail 用来描述 Job 的实现类及其他相关的静态信息；Trigger 做为作业的定时管理工具，一个 Trigger 只能对应一个作业实例，而一个作业实例可对应多个触发器；Scheduler 做为定时任务容器，是 Quartz 最上层的东西，它提携了所有触发器和作业，使它们协调工作，每个 Scheduler 都存有 JobDetail 和 Trigger 的注册，一个 Scheduler 中可以注册多个 JobDetail 和多个 Trigger。
 *
 * @PackageName cn.jly.boot.mp.config
 * @ClassName QuartzConfig
 * @Description 定时框架Quartz配置
 * @Author 姬岚洋
 * @Date 2021/1/5 下午 5:09
 */
@Configuration
public class QuartzConfig {

    /**
     * -------------------------------simpleScheduler------------------------------------------
     * 1. JobBuilder 无构造函数，只能通过 JobBuilder 的静态方法 newJob(Class jobClass)生成 JobBuilder 实例。
     * 2. withIdentity 方法可以传入两个参数 withIdentity(String name,String group) 来定义 TriggerKey，也可以不设置，
     * 像上文示例中会自动生成一个独一无二的 TriggerKey 用来区分不同的 Trigger。
     *
     * @return jobDetail
     */
    @Bean
    public JobDetail simpleJobDetail() {
        final JobDataMap map = new JobDataMap();
        map.put("name", "姬岚洋");

        return JobBuilder
                .newJob(QuartzSimpleSchedulerJob.class)
                .withIdentity(QuartzSimpleSchedulerJob.class.getSimpleName())
                .usingJobData(map)
                .storeDurably()
                .build();
    }

    @Bean
    public Trigger simpleJobTrigger(@Qualifier("simpleJobDetail") JobDetail simpleJobDetail) {
        final SimpleScheduleBuilder simpleScheduleBuilder = SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(2).repeatForever();

        return TriggerBuilder
                .newTrigger()
                .forJob(simpleJobDetail)
                .withIdentity("simpleJobTrigger")
                .withSchedule(simpleScheduleBuilder)
                .build();
    }

    /**
     * ---------------------------------------------cronScheduler---------------------------------------
     * 何时触发定时任务
     * <p>
     * 我们有两种方案来触发 CronSchedule 定时任务，一种是启动时调用 scheduleJobs() 来启动定时任务，
     * 另外一种方案使用 Spring Boot 自带的 Scheduled 在特定时间触发启动。
     */
    @Bean
    public CommandLineRunner commandLineRunner(CronSchedulerJob cronSchedulerJob) {
        return new CommandLineRunner() {
            @Override
            public void run(String... args) throws Exception {
                cronSchedulerJob.scheduleJobs();
                System.out.println(">>>>>>>>>>>>>>>>>>定时任务开始执行<<<<<<<<<<<<<<<<<<<<<<");
            }
        };
    }

    /**
     * 第二种方案，特定时间启动定时任务
     * <p>
     * 一般情况下，建议使用第一种方案来启动定时任务；
     * 第二种方案设置固定日期时，需要考虑重复启动定时任务的情况，重复启动定时任务会报错。
     * <p>
     * 注意，两种启动方案，在项目中选择一种使用即可，否则会导致重复启动定时任务而报错。
     */
//    @Scheduled(cron = "0 30 11 25 11 ?")
//    public void schedule() throws SchedulerException {
//        final CronSchedulerJob cronSchedulerJob = SpringContextUtils.getBean(CronSchedulerJob.class);
//        cronSchedulerJob.scheduleJobs();
//    }
}
